Zurück zum Artikel
Vorbereitung
Notizbuch herunterladen

Vorbereitung

Biblioteken

In [1]:
import numpy as np
import wave
# import matplotlib.pyplot as plt
# from scipy.fftpack import fft

# pynq Overlay
from pynq import Overlay
from pynq import allocate

# audio Codec driver module
from pynq.lib.audio import AudioADAU1761

Overlay

In [2]:
# overlay laden
ol = Overlay("Audio_duo_Filter_v1.bit")
# Check IP names
ol.ip_dict.keys()
dict_keys(['axi_dma_HP', 'axi_dma_TP', 'audio_codec_ctrl_0', 'processing_system7_0'])

Audio Codec Treiber

In [3]:
# Bezeichnung des Audio Codec Kontrollers
audio_description = ol.ip_dict['audio_codec_ctrl_0']
# Übergabe der Bezeichnung an Treiber
pAudio = AudioADAU1761(audio_description)
# Eintellen des Audiotreibers
pAudio.configure(sample_rate=48000, iic_index=1, uio_name='audio-codec-ctrl')

Funktionen

FormatChange(np.array)

Diese Funktion bereitet ein np.array so auf, dass es über den AXI-DMA an den Filter übertragen werden kann.
- Die Werte werden in 24-Bit-Integer konvertiert, entsprechend dem Format der Audiodatei. - Anschließend erfolgt eine Umwandlung in 32-Bit-Integer, da der AXI-DMA auf diese Datenbreite ausgelegt ist. - Die Daten werden in das Format uint32 angepasst, wie es für die Ein- und Ausgabepuffer erforderlich ist. - Als Ergebnis gibt die Funktion ein Array mit den konvertierten Werten zurück.

Diese Funktion wird innerhalb der Transmission verwendet.

Normierung(np.array)

Eine einfache Funktion welche Werte eines Arrays auf [-1.0, +1.0] normiert.
Diese Funktion wird innerhalb der Transmission verwendet.

Transmission(np.array, ip_buffer)

Diese Funktion übernimmt das Senden und Empfangen von Arrays über den AXI-DMA.
- Die Eingangsdaten werden zunächst mithilfe der Funktionen Normierung und Transmission in ein für den AXI-DMA geeignetes Format konvertiert. - Anschließend werden Input- und Outputbuffer über Pynq-Funktionen zugewiesen. Der Inputbuffer wird zusätzlich per Zero-Padding aufgefüllt. - Die vorbereiteten Eingangsdaten werden in den Inputbuffer geladen. - Der Sende- und Empfangskanal wird gestartet. Die Funktion wartet, bis alle Datenübertragungen abgeschlossen sind. - Es erfolgt eine Fehlerprüfung für beide Kanäle. - Die empfangenen Ausgangsdaten werden zurückgerechnet – Formatänderungen werden rückgängig gemacht und erneut normiert. - Abschließend werden Input- und Outputbuffer freigegeben - Die verarbeiteten Ausgangsdaten werden zurückgegeben.

Diese Funktion kann erst verwendet werden, nachdem der entsprechende AXI-DMA ausgewählt und initialisiert wurde.

Split2Packets(np.array, packet_size)

Diese Funktion unterteilt ein Array in kleinere Abschnitte mit definierter Größe.
Damit können Datensätze, die größer als der Puffer des Filters sind, in mehreren Schritten verarbeitet und gefiltert werden.

send2receive(Data_In)

Führt die Übertragungen für alle Teil-Arrays nacheinander aus.
Diese Funktion kann erst verwendet werden, nachdem der entsprechende AXI-DMA ausgewählt und initialisiert wurde.

read_wav(wav_path)

Hinweis: Diese Funktion wurde aus den Beispielen zu Audio aus den Pynq-Beispielen auf dem Board übernommen. Diese Funktion liest eine WAV-Datei ein und wandelt die Audiodaten in ein Format um, das weiterverarbeitet werden kann.
- Die Datei wird geöffnet, und es werden wichtige Informationen wie Anzahl der Frames, Kanäle, Abtastrate und Sample-Breite ausgelesen. - Die Audiodaten werden als Byte-Array geladen und in einen temporären Buffer einsortiert, der jedem Sample 4 Byte zuweist. - Die eigentlichen Audiodaten (z. B. 24-Bit = 3 Byte) werden an den Anfang jedes 4-Byte-Blocks kopiert. - Das Vorzeichen wird korrekt erweitert, sodass aus z. B. 24-Bit-Werten gültige 32-Bit-Werte entstehen. - Anschließend wird der Buffer als 32-Bit-Integer interpretiert und als mehrdimensionales Array zurückgegeben.

Die Funktion gibt zudem die Anzahl der Kanäle, die Abtastrate und die ursprüngliche Sample-Breite zurück.

save_to_24bit_wav(chan_l, chan_r, sample_rate, path)

Diese Funktion basiert auf read_wav.
Diese Funktion speichert zwei Audiokanäle (links und rechts) als 24-Bit-WAV-Datei.
- Die beiden Kanäle chan_l und chan_r werden zu einem Stereo-Array zusammengefügt. Erwartet wird, dass die Daten im Bereich [-1.0, 1.0] liegen (normalisierte Gleitkommazahlen). - Die Werte werden auf den gültigen Bereich begrenzt und anschließend in 24-Bit-Integer (als 32-Bit gespeichert) umgerechnet. - Die umgerechneten Integer-Werte werden in Bytes konvertiert, wobei nur die unteren 3 Byte (24 Bit) verwendet werden. - Anschließend wird die WAV-Datei im 24-Bit-Format erstellt: Stereo, 24 Bit, 48000 kHz - Die Rohdaten werden in die Datei geschrieben und die Datei gespeichert.

Diese Funktion ist besonders nützlich, um Audiodaten aus numpy-Arrays im professionellen 24-Bit-Format zu exportieren.

In [29]:
def FormatChange(x):
    x = x * (2**23)                            # In 24-Bit Integer, wie .wav-File (AudioCodec nimmt so auf!)
    x = x.astype(np.int32)                     # Als 32-Bit Integer interpretieren
    input_data = x.view(np.uint32)             # Für DMA als unsigned darstellen
    return input_data

# Normierung Wichtig für .wav-Files!
def Normierung(x):
    m = np.max(np.abs(x))
    x_n = x / m 
    return x_n

def Transmission(input_data,ip_buffer):
    # Festlegen der Größen
    buffer_size = int(ip_buffer)
    # print("Buffer Size: ", buffer_size)
    input_data = FormatChange(Normierung(input_data))
    # input_data = FormatChange(input_data)
    data_size = int(len(input_data))
    # print('Data Size: ', data_size)
    
    # Padding
    pad = np.zeros(ip_buffer)
    pad_frame = FormatChange(pad)
    # print('Frame Length: ', len(pad_frame),' / ', 'Frame Type: ', type(pad_frame))
    
    # Leere Buffer
    input_buffer = allocate(shape=(buffer_size,), dtype=np.uint32)
    output_buffer = allocate(shape=(buffer_size,), dtype=np.uint32)
    
    # Padding Inputbuffer
    input_buffer[:] = pad_frame
    
    # Laden der Daten in Inputbuffer
    input_buffer[: data_size] = input_data
    # print('Input Buffer: ', input_buffer[: data_size])
    
    # Senden un Empfangen der Daten
    dma.sendchannel.transfer(input_buffer)
    dma.recvchannel.transfer(output_buffer)
    dma.sendchannel.wait()
    dma.recvchannel.wait()
    
    # check status
    #print("Recv Status: ","Error: ", dma_recv.error, "Idle: ", dma_recv.idle, "Running: ", dma_recv.running)
    #print("Send Status: ","Error: ", dma_send.error, "Idle: ", dma_send.idle, "Running: ", dma_send.running)
    
    # print('Output Buffer: ', output_buffer[: data_size])
    
    # Check for Error
    # if dma_recv.error == False and dma_send.error == False:
        # print('>>>> Transmission successful <<<<')
    if dma_recv.error == True or dma_send.error == True:
        print('!!!>> Error in Transmission <<!!!')
    
    # Umrechnen der Empfangenen Daten
    output_data = np.array(output_buffer[: data_size]).view(np.int32)  # zurück zu signed int32
    # output_data = np.array(output_buffer).view(np.int32)  Test des Kompletten Frame
    y = output_data / (2**23)
    y = np.array(y)   # zu np.array
    y = Normierung(y) # ausgabe normieren
    
    # Buffer leeren
    del input_buffer, output_buffer
    # print('Buffer Clear')
    # print('>-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-<')
    
    return y
    
def Split2Packets(data,packet_size):
    packets = []
    for i in range(0, len(data), packet_size):
        packet = data[i:i+packet_size]
        packets.append(packet)
    return packets

def send2receive(Data_In):
    ip_buffer = 2**18
    # Filtern Kanal
    Data_Out = []

    # Zerteilung und Übertragung in Packeten
    Packets = Split2Packets(Data_In, ip_buffer)
    anz_trans = 0
    for packet in Packets:
        result = Transmission(packet, ip_buffer)
        Data_Out.extend(result)
        anz_trans = anz_trans + 1
    print(anz_trans, "Transmissions")
    return Data_Out
        
def read_wav(wav_path):
    with wave.open(wav_path, 'r') as wav_file:
        raw_frames = wav_file.readframes(-1)
        num_frames = wav_file.getnframes()
        num_channels = wav_file.getnchannels()
        sample_rate = wav_file.getframerate()
        sample_width = wav_file.getsampwidth()
    
    temp_buffer = np.empty((num_frames, num_channels, 4), dtype=np.uint8)
    raw_bytes = np.frombuffer(raw_frames, dtype=np.uint8)
    temp_buffer[:, :, :sample_width] = raw_bytes.reshape(-1, num_channels, 
                                                    sample_width)
    temp_buffer[:, :, sample_width:] = \
    (temp_buffer[:, :, sample_width-1:sample_width] >> 7) * 255
    frames = temp_buffer.view('<i4').reshape(temp_buffer.shape[:-1])
    
    print("Frames:",len(frames), "Channels:", num_channels, "Sample Rate:",sample_rate, "Sample Width", sample_width )
    return frames, num_channels, sample_rate, sample_width

def save_to_24bit_wav(chan_l, chan_r, sample_rate, path):
    # Annahme: frames.shape = (num_frames, num_channels)
    # Typ: float64 in [-1.0, 1.0]
    frames = np.stack((chan_l, chan_r), axis=1) 
    max_val = 2**23 - 1  # 24-bit max signed int
    frames = np.clip(frames, -1.0, 1.0)
    frames_int = (frames * max_val).astype(np.int32)

    # In Bytes umwandeln
    temp_bytes = frames_int.reshape((*frames.shape, 1)).view(np.uint8)
    raw_bytes = temp_bytes[:, :, :3].reshape(-1)

    with wave.open(path, 'wb') as wav_out:
        wav_out.setnchannels(frames.shape[1])
        wav_out.setsampwidth(3)  # 24-bit
        wav_out.setframerate(sample_rate)
        wav_out.writeframes(raw_bytes.tobytes())
        
def UseFilter(in_Name, out_Name):
    import time
    start = time.time()
    [frames, channels, Fs, Fw] = read_wav(in_Name)
    # Read Data
    data_l = frames[:,0]
    data_r = frames[:,1]
    print("Start Sending left Chanal")
    Data_Out_L = send2receive(data_l)
    print("Start Sending right Chanal")
    Data_Out_R = send2receive(data_r)
    save_to_24bit_wav(Data_Out_L, Data_Out_R, Fs, out_Name)
    end = time.time()
    print("Finished")
    print(f"Dauer: {end - start:.2f} Sekunden")
    
    

Audio Aufnahme und Ausgabe über Pynq-Z2

Lautstärke und Input

Alle Funktionen zu den Audio Codec auf dem Pynq-Z2 lassen sich auf Docs » pynq Package » pynq.lib Package » pynq.lib.audio Module finden.

  • Mit set_volume() wird die Ausgangslautstärke eingestellt. Der Wertebereich reicht von 0 (−57 dB) bis 63 (+6 dB). Die Eingangslautstärke kann nicht geregelt werden und ist von der jeweiligen Signalquelle abhängig.
  • select_line_in() wählt den LINE_IN-Port auf dem Board als Audioeingang. Die Ausgabe erfolgt über den kombinierten Kopfhörer-/Mikrofonanschluss (HP+MIC).
  • select_microphone() wählt den MIC-Anschluss des Boards als Eingangsquelle. Dabei handelt es sich um einen Kombianschluss: Wird ein Headset angeschlossen, kann dessen Mikrofon als Eingang und die Kopfhörer als Ausgang verwendet werden. Sollten Ein- und Ausgang getrennt verwendet werden, ist ein entsprechender Splitter erforderlich.
  • play() gibt die aktuell im Puffer befindliche Audiodatei über den HP+MIC-Kombianschluss wieder.
  • load(file) lädt eine Audiodatei im .wav-Format in den internen Puffer. Die Datei sollte in 24-Bit bei 48 kHz Samplingrate vorliegen.
  • record(sec) zeichnet Audio über den Audiocontroller in den internen Puffer auf. Die Aufnahme erfolgt in 24-Bit bei einer Abtastrate von 48 kHz.
  • save(file) speichert den aktuellen Inhalt des Puffers als .wav-Datei.
In [5]:
# einstellen der Lautsärke
pAudio.set_volume(40) # Set output volume of ADAU1761.

# Einstellen eingang: LineIn
pAudio.select_line_in()
In [1]:
# einstellen der Lautsärke
pAudio.set_volume(30)   
# Einstellen eingang: HP/MIC
pAudio.select_microphone()
In [6]:
# Aufnahme
recTime = 60
pAudio.record(recTime)
# in pAudio.buffer werden die aufnahmen gespreichert
print(pAudio.buffer, type(pAudio.buffer))
[   11005     8602    13042 ... 16765226 16768680 16763665] <class 'numpy.ndarray'>
In [2]:
# Buffer ausspielen
pAudio.play()
In [7]:
pAudio.save("record_2.wav")
In [8]:
from IPython.display import Audio as IPAudio
IPAudio("record_2.wav")

Filter

Die dma Zuweisungen können nicht innerhalb Funktionen initialisiert werden, da diese außerhalb benötigt werden.

In [30]:
# Zuweisung für dma
dma = ol.axi_dma_TP
dma_send = ol.axi_dma_TP.sendchannel
dma_recv = ol.axi_dma_TP.recvchannel
In [33]:
# Zuweisung für dma
dma = ol.axi_dma_HP
dma_send = ol.axi_dma_HP.sendchannel
dma_recv = ol.axi_dma_HP.recvchannel
In [34]:
UseFilter("record_2.wav", "filt_rec_2.wav")
Frames: 2880000 Channels: 2 Sample Rate: 48000 Sample Width 3
Start Sending left Chanal
11 Transmissions
Start Sending right Chanal
11 Transmissions
Finished
Dauer: 18.32 Sekunden
In [35]:
from IPython.display import Audio as IPAudio
IPAudio("filt_rec_2.wav")